package com.linkai.JUC._04_unsafecollection;

import com.linkai.源码.HashMap;
import com.linkai.源码.LinkedHashMap;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Description:
 *               ConcurrentModificationException
 *               HashMap 线程不安全，考虑线程安全的话可以使用 HashTable，Collections.synchronizedMap;
 *               但是 HashTable 和 Collections.synchronizedMap 无论是读写操作，都会给整个集合对象加锁，导致其他线程阻塞，效率不高。
 *               HashMap与ConcurrentHashMap总结：
 *                  https://blog.csdn.net/qq_42082161/article/details/114846075
 *
 * @Author: 林凯
 * @Date: 2021/10/24 14:54
 */
public class MapTest {
    // map 是这样用的吗？默认等价于什么？不是，工作中不用 HashMap
    public static void main(String[] args) {
        /*
            1. 使用 HashMap 可能会抛出 ConcurrentModificationException 异常（并发修改异常）
            因为 HashMap 是线程不安全的
        * */
        Map<String, String> hashMap = new HashMap<>();

        /*
            2. 使用 Collections.synchronizedMap(new HashMap<>());
            使用 synchronize 保证线程同步，对整个集合对象加锁；
            对象级别的同步，性能有所下降；
            synchronizedMap 内部会创建一个 SynchronizedMap 对象，该对象内部维护了一个Map和一个互斥锁 mutex。
            构造方法需要传入一个 Map 或者 mutex，如果不传入 mutex，则将对象排斥锁赋值为this。
            在调用方法时使用 synchronized 来保证线程同步,当然了实际上操作的还是我们传入的HashMap实例，
            里面的 synchronized 锁住的就是 mutex
        * */
        Map<String, String> synchronizedMap = Collections.synchronizedMap(new HashMap<>());

       /*
            3. HashTable 数组 + 链表
            使用 synchronize 保证线程安全，同样也是对整个集合对象加锁。
        * */
        Hashtable<String, String> hashTable = new Hashtable<>();

        Map<String, String> concurrentHashMap = new ConcurrentHashMap<>();
        /*
            4. ConcurrentHashMap 是线程安全的

                ** 插入操作流程图：https://www.processon.com/view/60c8047263768966f7df9b33
                https://www.processon.com/view/60af0a206376893238e00e2a
                知乎：https://zhuanlan.zhihu.com/p/97902016

            jdk1.7版本: 比HashMap多出了一个类Segment，而Segment是一个可重入锁（ReentrantLock）。
                每次 put 先定位到 Segment，然后再定位到具体的数组位置
                        ConcurrentHashMap是使用了锁分段技术技术来保证线程安全的。
                        锁分段技术：首先将数据分成一段一段的存储，然后给每一段数据配一把锁，
                        当一个线程占用锁访问其中一个段数据的时候，其他段的数据也能被其他线程访问。

            jkd1.8版本：使用 CAS + synchronize 保证并发安全性
                使用 volatile 修饰了 Node 类里面的 val 和 next 字段，保证了可见性。

                （1） put 操作时
                   1. 计算 key 的 hash 值
                   2. 遍历数组 Table，如果 Table 为空，则进行初始化操作
                   3. 如果数组的第 i 个位置为 null，则说明没有发生碰撞，执行 CAS 操作直接在该位置插入新值,失败这自旋保证成功
                     3.1 插入之后判断是否需要扩容
                   4. 如果数组第 i 个位置不为null，还有判断是否真正发生扩容，如果是，则加入一起帮助扩容（支持多线程扩容）
                   5. 如果没发生扩容，则用 synchronize 对数组第i个位置加锁（即锁住链表的头结点或者红黑树的根节点）
                   6. 然后执行插入操作，链表or红黑树

               （2）size操作
               在没有并发的情况下，使用一个 baseCount volatile 变量就足够了。
               当并发的时候，CAS 修改 baseCount 失败后，就会使用 CounterCell 类了，会创建一个这个对象，通常对象的 volatile value 属性是 1。
               在计算 size 的时候，会将 baseCount 和 CounterCell 数组中的元素的 value 累加，得到总的大小，但这个数字仍旧可能是不准确的。



        * */

        /*
            5. LinkedHashMap
                LinkedHashMap 继承自 HashMap，基于 HashMap 和双向链表来实现的
                HashMap无序；LinkedHashMap有序，可分为插入顺序和访问顺序两种。
                （1）插入顺序：先添加的在前面，后添加的在后面。修改操作不影响顺序
                （2）访问顺序：谓访问指的是get/put操作，对一个键执行get/put操作后，
                     其对应的键值对会移动到链表末尾，所以最末尾的是最近访问的，最开始的是最久没有被访问的，这就是访问顺序。
                 访问顺序可以通过制定 accessOrder 来决定。
        * */
        testLinkedHashMap();


        // 加载因子，初始化容量
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                concurrentHashMap.put(String.valueOf(Thread.currentThread().getName()), UUID.randomUUID().toString().substring(0, 5));
                System.out.println(concurrentHashMap);
            }, String.valueOf(i)).start();
        }

        while (Thread.activeCount() > 2) {
            // 除了main线程，和gc线程之外，还有线程在执行的话，就一直循环
            // 保证打印最后的长度的时候，其他线程已经执行完毕了。
        }
        System.out.println(concurrentHashMap.size());

    }


    static void testLinkedHashMap() {
        /*
            第三个参数用于指定accessOrder值；
            accessOrder 为 false 时，访问顺序就是插入顺序；
            accessOrder 为 true 时，每次读取一个元素之后，该元素就会放到链表的末尾，所以访问顺序可能发生改变。
        * */
        LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<String, String>(10, 0.75f, true);
        linkedHashMap.put("k1", "v1");
        linkedHashMap.put("k2", "v2");
        Iterator<Map.Entry<String, String>> iterator = linkedHashMap.entrySet().iterator();
        System.out.println("------插入顺序-------");
        while (iterator.hasNext()) {
            Map.Entry<String, String> next = iterator.next();
            System.out.println(next.getKey());
            System.out.println(next.getValue());
        }
        System.out.println("--------访问元素-------");
        System.out.println(linkedHashMap.get("k1"));
        System.out.println("--------访问之后的顺序-----");
        // 顺序是：k2 v2,k1 v1
        Iterator<Map.Entry<String, String>> iterator2 = linkedHashMap.entrySet().iterator();
        while (iterator2.hasNext()) {
            Map.Entry<String, String> next = iterator2.next();
            System.out.println(next.getKey());
            System.out.println(next.getValue());
        }
    }
}
